package de.imdb250junkie.db;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JProgressBar;

import de.imdb250junkie.imdb.Top250Reader;
import de.imdb250junkie.imdb.Top250Reader.MovieNotFoundException;
import de.imdb250junkie.utils.Movie;
import de.imdb250junkie.utils.State;

/**
 * Class for manging local movie database.
 * 
 * @author André Stannek
 * @version 2010-02-10
 */
public class DB extends HashSet<Movie>
{
    /**
     * 
     */
    private static final long serialVersionUID = -52828898421620929L;

    /**
     * The array of the present top250.
     */
    private transient Movie[] top250;

    /**
     * length of top250.
     */
    public static final int TOP_LENGTH = 250;

    /**
     * loads the old database or creates a new one if impossible.
     */
    public DB()
    {
        super();
        top250 = new Movie[TOP_LENGTH];
    }

    /**
     * Searches for a movie in the database.
     * 
     * @param movie
     *            movie to search
     * @return the movie from the database or null if not existent
     */
    public final Movie getMovie(final Movie movie)
    {
        for (final Movie m : this)
        {
            if (m.equals(movie))
            {
                return m;
            }
        }
        return null;
    }

    /**
     * wrapper Method for {@link Set#add(Object)} saving the database after each
     * call.
     * 
     * @param movie
     *            the movie to add
     * @return true if adding was successful
     */
    @Override
    public final boolean add(final Movie movie)
    {
        final boolean result = super.add(movie);
        save();
        return result;
    }

    /**
     * refereshes the database from imdb
     * 
     * @param progress
     *            ProgressBar which shows the update status
     * @throws UpdateFailedException
     *             if top250 could not be loaded from imdb
     * @throws IOException
     *             on IO-Error
     * @throws InterruptedException
     *             if process was interupted
     * @throws MovieNotFoundException
     *             if a movie was not found on imdb
     */
    public final void update(final JProgressBar progress)
            throws UpdateFailedException, MovieNotFoundException,
            InterruptedException, IOException
    {

        final Movie[] newtop = Top250Reader.read(progress);
        // reset rank of all movies in database
        for (final Movie m : this)
        {
            m.setRank(-1);
        }

        for (final Movie topMovie : newtop)
        {
            if (!add(topMovie))
            {
                final Movie oldMovie = getMovie(topMovie);
                oldMovie.setRank(topMovie.getRank());
                oldMovie.setRating(topMovie.getRating());
                newtop[topMovie.getRank()] = oldMovie;
            }
        }
        makeTop();
    }

    /**
     * Creates the top250 from the present database
     */
    public void makeTop()
    {
        top250 = new Movie[TOP_LENGTH];
        for (final Movie m : this)
        {
            final int rank = m.getRank();
            if (rank > 0 && rank <= TOP_LENGTH)
            {
                top250[rank - 1] = m;
            }
        }
    }

    /**
     * @return the top250
     */
    public final Movie[] getTop()
    {
        return top250.clone();
    }

    /**
     * Speichter die Datenbank.
     */
    public final void save()
    {
        save("db.ser");
    }

    /**
     * Speichter die Datenbank.
     * 
     * @param path
     *            Dateipfad an den die serialisierte Datenbankdatei gespecihert
     *            wird.
     */
    public final void save(final String path)
    {
        try (ObjectOutputStream objectOut = new ObjectOutputStream(
                new FileOutputStream(path)))
        {
            objectOut.writeObject(this);
        }
        catch (final IOException e)
        {
            Logger.getLogger("ERROR").log(Level.SEVERE,
                    "Database could not be saved", e);
        }
    }

    /**
     * Speichert den neuen Status eines Filmes in der Datenbank.
     * 
     * @param movie
     *            der Film
     * @param state
     *            der neue Status
     */
    public final void saveMovieState(final Movie movie, final State state)
    {
        getMovie(movie);
        movie.setState(state);
        save();
    }

    /**
     * Exception to be thrown if database update is unsuccessful.
     * 
     * @author André Stannek
     * @version 2010-02-09
     */
    public static class UpdateFailedException extends Exception
    {
        /**
         * genrated UID
         */
        private static final long serialVersionUID = 56501721837651522L;
    }
}
